package com.fary.security.config.annotation.authentication.configuration;

import com.fary.context.ApplicationContext;
import com.fary.core.Ordered;
import com.fary.core.annotation.Order;
import com.fary.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import com.fary.security.crypto.PasswordEncoder;

@Order(InitializeUserDetailsBeanManagerConfigurer.DEFAULT_ORDER)
class InitializeUserDetailsBeanManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {

    static final int DEFAULT_ORDER = Ordered.LOWEST_PRECEDENCE - 5000;

    private final ApplicationContext context;

    /**
     * @param context
     */
    InitializeUserDetailsBeanManagerConfigurer(ApplicationContext context) {
        this.context = context;
    }

    @Override
    public void init(AuthenticationManagerBuilder auth) throws Exception {
        auth.apply(new InitializeUserDetailsManagerConfigurer());
    }

    class InitializeUserDetailsManagerConfigurer extends GlobalAuthenticationConfigurerAdapter {
        @Override
        public void configure(AuthenticationManagerBuilder auth) throws Exception {
            if (auth.isConfigured()) {
                return;
            }
            UserDetailsService userDetailsService = getBeanOrNull(UserDetailsService.class);
            if (userDetailsService == null) {
                return;
            }

            PasswordEncoder passwordEncoder = getBeanOrNull(PasswordEncoder.class);
            UserDetailsPasswordService passwordManager = getBeanOrNull(UserDetailsPasswordService.class);

            DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
            provider.setUserDetailsService(userDetailsService);
            if (passwordEncoder != null) {
                provider.setPasswordEncoder(passwordEncoder);
            }
            if (passwordManager != null) {
                provider.setUserDetailsPasswordService(passwordManager);
            }
            provider.afterPropertiesSet();

            auth.authenticationProvider(provider);
        }

        /**
         * @return a bean of the requested class if there's just a single registered component, null otherwise.
         */
        private <T> T getBeanOrNull(Class<T> type) {
            String[] beanNames = InitializeUserDetailsBeanManagerConfigurer.this.context.getBeanNamesForType(type);
            if (beanNames.length != 1) {
                return null;
            }

            return InitializeUserDetailsBeanManagerConfigurer.this.context.getBean(beanNames[0], type);
        }
    }
}